package network

import (
	"bytes"
	"encoding/binary"
	"encoding/gob"
	"errors"
	"github.com/Unixeno/TheMoon/moon/zone/storage"
	"github.com/Unixeno/TheMoon/pool"
	"net"
)

var (
	SubnetExisted    = errors.New("subnet exists")
	InvalidMask      = errors.New("invalid mask")
	NoHistoryNetwork = errors.New("no history network conf")
)

type IPManager struct {
	Subnets map[string]*IPAllocator
}

type IPAllocator struct {
	IP       string
	Begin    uint32
	End      uint32
	IPBucket []uint32
}

func InitManager() *IPManager {
	ipam := &IPManager{Subnets: map[string]*IPAllocator{}}
	if err := ipam.load(); err != nil && err != NoHistoryNetwork {
		panic(err)
	}
	return ipam
}

func (ipam *IPManager) Register(name string, CIDR net.IPNet) error {
	logger.Debug("IPManager register ", name)
	if subnet, ok := ipam.Subnets[name]; ok {
		if subnet.IP != CIDR.IP.String() {
			// 相同网卡,配置不同网段
			return SubnetExisted
		} else {
			return nil
		}
	}

	//	不同网卡配置相同网段
	for _, subnet := range ipam.Subnets {
		if subnet.IP == CIDR.IP.String() {
			return SubnetExisted
		}
	}

	if ones, nums := CIDR.Mask.Size(); ones == 0 {
		return InvalidMask
	} else {

		sub := &IPAllocator{
			IP:       CIDR.IP.String(),
			Begin:    IPToUInt32(CIDR.IP) + 1,
			End:      IPToUInt32(CIDR.IP) + uint32(nums-1),
			IPBucket: make([]uint32, 0, 256),
		}

		ipam.Subnets[name] = sub
		if err := ipam.dump(); err != nil {
			return err
		}
	}
	return nil
}

func (ipam *IPManager) Unregister(name string) error {
	delete(ipam.Subnets, name)
	if err := ipam.dump(); err != nil {
		return errors.New("failed to save network change")
	}
	return nil
}

func UInt32ToIP(intIP uint32) net.IP {
	var res = make([]byte, 4)
	binary.BigEndian.PutUint32(res, intIP)
	return net.IPv4(res[0], res[1], res[2], res[3])
}

func IPToUInt32(ip net.IP) uint32 {
	return uint32(binary.BigEndian.Uint32(ip.To4()))
}

func (allocator *IPAllocator) isAllocatorEmpty() bool {
	return allocator.End <= allocator.Begin
}

func (allocator *IPAllocator) isBucketEmpty() bool {
	return len(allocator.IPBucket) <= 0
}

func (allocator *IPAllocator) allocateFromBucket() (net.IP, error) {
	if allocator.isBucketEmpty() {
		return nil, errors.New("bucket empty")
	} else {
		length := len(allocator.IPBucket)
		ip := UInt32ToIP(allocator.IPBucket[length-1])
		allocator.IPBucket = allocator.IPBucket[:length-1]
		return ip, nil
	}
}

func (allocator *IPAllocator) allocateFromAllocator() (net.IP, error) {
	if allocator.isAllocatorEmpty() {
		return nil, errors.New("allocator empty")
	} else {
		ipInt := allocator.End - 1
		allocator.End -= 1
		return UInt32ToIP(ipInt), nil
	}
}

func (allocator *IPAllocator) allocate() (net.IP, error) {
	var (
		ip  net.IP
		err error
	)
	if ip, err = allocator.allocateFromBucket(); err != nil {
		if ip, err = allocator.allocateFromAllocator(); err != nil {
			return nil, err
		}
	}
	return ip, err
}

func (allocator *IPAllocator) release(ip net.IP) {
	ipInt := IPToUInt32(ip)
	allocator.IPBucket = append(allocator.IPBucket, ipInt)
}

func (allocator *IPAllocator) getGateway() net.IP {
	return UInt32ToIP(allocator.Begin - 1)
}

// Allocate return ip, gateway, error
func (ipam *IPManager) Allocate(name string) (net.IP, net.IP, error) {

	var ip, gateway net.IP

	if subnet, ok := ipam.Subnets[name]; !ok {
		return nil, nil, errors.New("network not existed")
	} else {
		var err error
		if ip, err = subnet.allocate(); err != nil {
			return nil, nil, err
		} else {
			gateway = subnet.getGateway()
		}
	}

	if err := ipam.dump(); err != nil {
		return nil, nil, errors.New("failed to save network change")
	}

	return ip, gateway, nil
}

func (ipam *IPManager) Release(name string, ip net.IP) error {
	subnet := ipam.Subnets[name]
	subnet.release(ip)
	if err := ipam.dump(); err != nil {
		return errors.New("failed to save network change")
	}
	return nil
}

func (ipam *IPManager) serialize() ([]byte, error) {
	buf := pool.Fixed64KPool.Get().([]byte)
	defer pool.Fixed64KPool.Put(buf)
	bytesBuf := bytes.NewBuffer(buf)
	bytesBuf.Reset()
	enc := gob.NewEncoder(bytesBuf)
	err := enc.Encode(ipam)
	if err != nil {
		return nil, err
	} else {
		return bytesBuf.Bytes(), nil
	}
}

func (ipam *IPManager) unSerialize(data []byte) error {
	dec := gob.NewDecoder(bytes.NewBuffer(data))
	err := dec.Decode(ipam)
	if err != nil {
		return err
	}
	return nil
}

func (ipam *IPManager) load() error {
	data, ok := storage.LoadNetwork()
	if !ok {
		return NoHistoryNetwork
	}

	return ipam.unSerialize(data)
}

func (ipam *IPManager) dump() error {
	data, err := ipam.serialize()
	if err != nil {
		return err
	} else {
		if err := storage.DumpNetwork(data); err != nil {
			return err
		}
	}
	return nil
}
